<?php

abstract class AbstractTableAccessor {

    /**
     * @return AbstractDbConnection
     */
    protected $connection;

    final public function __construct(AbstractDbConnection $dbConnection) {
        $this->connection = $dbConnection;
        $this->checkSelfCorrect();
    }

    public function beginTransaction()    { $this->connection->beginTransaction(); }
    public function commitTransaction()   { $this->connection->commitTransaction(); }
    public function rollbackTransaction() { $this->connection->rollbackTransaction(); }
    public function isTableExists()       { return $this->connection->isTableExists( $this->getTableName() ); }

	abstract protected function getFieldNames();
	abstract protected function getTableName();
	abstract public function getKeyFieldName();

	/**
	 * @return AbstractTableRecord
	 */
	abstract function createRecord($id = null);

	final protected function checkSelfCorrect() {
		if (!$this->getKeyFieldName() ||
			!in_array($this->getKeyFieldName(), $this->getFieldNames())
		) throw new Exception('incorrect table primary key');
	}

	public function getRecordsAsHash($oCond = null, PageOuter $oPageOuter = null) {

		if ($oCond === null) $oCond = DbCondition::createNull();

		if ($oPageOuter) {

            $arrResult = $this->connection->query(
                'SELECT count(*) as total ' .
                'FROM ' . $this->getTableName() . $oCond->getSQL(),
                $oCond->getParams()
            );

            $total = $arrResult[0]['total'];

            $oPageOuter->setTotalItems($total);

    		$oCond->setLimit(
                $oPageOuter->getDiapasone()->getFrom(),
                $oPageOuter->getDiapasone()->getNum()
            );
		}

		$arrResult = $this->connection->query(
			'SELECT * ' .
			'FROM ' . $this->getTableName() . $oCond->getSQL(),
			$oCond->getParams()
		);

	    $arrResult = $this->sripslashes($arrResult);

        return $arrResult;
	}

	public function getRecordsAsObjects($oCond = null, PageOuter $oPageOuter = null) {

		$arrObjects = array();
		foreach ($this->getRecordsAsHash($oCond, $oPageOuter) as $hData) {

			$record = $this->createRecord($hData[$this->getKeyFieldName()]);
			$record->updateFromHash($hData);

			$arrObjects[] =  $record;
		}

		return $arrObjects;
	}

	public function deleteRecordByKeyField($key) {
		$this->deleteRecords(DbCondition::createSimpleEqual($this->getKeyFieldName(), $key));
	}

	public function deleteRecords(DbCondition $oCond = null) {

	    if ($oCond == null) $oCond = DbCondition::createNull();

		$this->connection->query(
			'DELETE FROM ' . $this->getTableName() . $oCond->getSQL(),
			$oCond->getParams()
		);
	}

	public function getUniqueRecordIfExists($field, $value) {

        $arr = $this->getRecordsAsObjects(DbCondition::createSimpleEqual($field, $value));

        if (count($arr) > 1)
            throw new Exception('not unique record '/* . $oCond->getSQL()*/);

        return (count($arr) == 0) ?
            null :
            reset($arr);
	}

	/**
	 * @return AbstractTableRecord
	 */
	protected function getRecordByKeyField($key, $throwException = true) {

	    $record = $this->getUniqueRecordIfExists($this->getKeyFieldName(), $key);

	    if (!$record) {

		    if ($throwException)
                throw new Exception('Can not select from ' . get_class($this) . ' with key field = "' . $key . '"');

            return false;
		}

		return $record;
	}

	public function updateRecord(AbstractTableRecord $oRecord) {

		$sSet = '';
//		Debug::show('$oRecord->getHash()',$oRecord->getHash());
//		Debug::show('$oRecord->getKey()',$oRecord->getKey());
//		Debug::show('$oRecord->getKey()',$oRecord->getAccessor()->getKeyFieldName());

		foreach ($oRecord->getHash() as $fieldName => $fieldValue) {

		    //Debug::show($this->getKeyFieldName(),$fieldName );
			if (!in_array($fieldName, $this->getFieldNames())) continue;
			if ($fieldName == $this->getKeyFieldName()) continue;

			$arrDataToSet[] = $fieldValue;

			if ($sSet != '') $sSet .= ', ';
			$sSet .= $fieldName . ' = ? ';
		}

		$this->connection->query(
			'UPDATE ' . $this->getTableName() . ' ' .
			'SET ' . $sSet .
			'WHERE ' . $this->getKeyFieldName() . ' = ?',
			array_merge($arrDataToSet, array($oRecord->getKey()))
		);
	}

	public function insertRecordsArray(array $arrRecords) {

		$keyFieldName = $this->getKeyFieldName();

		foreach ($arrRecords as $hRecord) {

			$rec = $this->createRecord($hRecord[$keyFieldName]);
			$rec->updateFromHash($hRecord);

			$this->insertRecord($rec);
		}
	}

	/**
	 * @return AbstractTableRecord
	 */
	public function insertRecord(AbstractTableRecord $oRecord) {

		if (get_class($this) != get_class($oRecord->getAccessor()))
			throw new Exception('Accesors class name mismatch: ' . get_class($this) . ' get, ' . get_class($oRecord->getAccessor()) . ' must be');

		$sFields = '';
		$sValues = '';
		$arrDataToSet = array();

		foreach ($oRecord->getHash() as $fieldName => $fieldValue) {

			if (!in_array($fieldName, $this->getFieldNames())) continue;

			if (($fieldName == $this->getKeyFieldName()) &&
				($oRecord->getKey() === null)
			) {
				continue;
			}

			$arrDataToSet[] = $fieldValue;

			if ($sFields != '') {
				$sFields .= ', ';
				$sValues .= ', ';
			}
			$sFields .= $fieldName;
			$sValues .= '?';
		}

		$this->connection->query(
			'INSERT INTO ' . $this->getTableName() . ' ' .
			'(' . $sFields . ') ' .
			'VALUES  (' . $sValues . ')',
			$arrDataToSet
		);

		$oInsertedRecord = $this->getRecordByKeyField($this->connection->getLastInsertedRowId());

		return $oInsertedRecord;
	}

	public function dropTable() {
        $this->connection->query('DROP TABLE ' . $this->getTableName());
	}

	public function createStructure() {

		$arrFieldsDescription = array();
		foreach ($this->getFieldNames() as $fieldName) {
			$arrFieldsDescription[] = ($fieldName == $this->getKeyFieldName()) ?
				"$fieldName INTEGER PRIMARY KEY" :
				$fieldName;
		}

		$this->connection->query('
			CREATE TABLE ' . $this->getTableName() .
			' (' . implode(',', $arrFieldsDescription) . ')'
		);
	}

	public function isRecordExists(DbCondition $oCond = null) {
	   return $this->getCount($oCond) > 0;
	}

	public function getCount(DbCondition $oCond = null) {

		if ($oCond === null) $oCond = DbCondition::createNull();

		$arrResult = $this->connection->query('
			SELECT COUNT(*) AS number
			FROM ' . $this->getTableName() . $oCond->getSQL(),
			$oCond->getParams()
		);

		return $arrResult[0]['number'];
	}

	final public function checkFieldExists($fieldName) {
		if (!in_array($fieldName,$this->getFieldNames()))
			throw new Exception("Field '$fieldName' not exists @ " . $this->getTableName());
	}

	protected function sripslashes($var) {

	    if (!is_array($var))
	       return strtr($var, array('\"' => '"'));

	    foreach ($var as $key => $val) {
	        $var[$key] = $this->sripslashes($val);
	    }

	    return $var;
	}
}
?>